﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace MetaView
{
    /// <summary>
    /// The TimeSlider control acts like a trackbar, except that it uses dates 
    /// instead of integers. It also supports the showing of a highlighted time
    /// segment within the trackbar, to show a highlighted period of time.
    /// </summary>
    [ToolboxBitmap(typeof(TrackBar))]
    public class TrackBar : System.Windows.Forms.TrackBar
    {

        #region Member variables

        private int minValue;					// minimum date value
        private int maxValue;			    	// maximum date value
        private int currentValue;				// current date value
        private int smallChange;					// small trackbar change
        private int largeChange;					// large trackbar change
        private int tickFrequency;				// tick frequency
        private Graphics mGraphics = null;			// the graphics object for the base trackbar

        private Label mMinLabel;					// label for min value
        private Label mMaxLabel;					// label for max value
        private Label mCurLabel;					// label for current value

        // these define where on the trackbar we place the segment. I never 
        // found a place that I found to be 'ideal', so I just made it easy
        // to change in the code.
        private const int SEGMENT_YPOS = 15;
        private const int SEGMENT_HEIGHT = 5;
        
        private bool mbShowMinLabel = false;		// show min label flag
        private bool mbShowMaxLabel = false;		// show max label flag
        private bool mbShowCurLabel = true;			// show current label flag

        private bool mbSuspendLayout = false;		// ability to change min, max and current value within inter-dependant checks
        
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor for class. Initializes all our internal variables.
        /// </summary>
        public TrackBar()
        {
            minValue = base.Minimum;
            maxValue = base.Maximum;
            smallChange = base.SmallChange;
            largeChange = base.LargeChange;
            tickFrequency = base.TickFrequency;
            currentValue = minValue;

            mGraphics = Graphics.FromHwnd(base.Handle);

            mMinLabel = new System.Windows.Forms.Label();
            mMinLabel.AutoSize = true;
            if (base.Orientation != Orientation.Horizontal)
                mMinLabel.Visible = false;
                mMinLabel.Location = new System.Drawing.Point(Width - mMinLabel.Width, Height-13);
            mMinLabel.Name = "lblMinValue";
            mMinLabel.Size = new System.Drawing.Size(0, 13);
            mMinLabel.ForeColor = Color.White;

            mMaxLabel = new System.Windows.Forms.Label();
            mMaxLabel.AutoSize = true;
            if (base.Orientation != Orientation.Horizontal)
                mMaxLabel.Visible = false;
                mMaxLabel.Location = new System.Drawing.Point(Width - 80, Height - 13);
            mMaxLabel.Name = "lblMaxValue";
            mMaxLabel.TextAlign = ContentAlignment.TopRight;
            mMaxLabel.Anchor = AnchorStyles.Right;
            mMaxLabel.Size = new System.Drawing.Size(0, 80);
            mMaxLabel.ForeColor = Color.White;

            mCurLabel = new System.Windows.Forms.Label();
            mCurLabel.AutoSize = true;
            if (base.Orientation == Orientation.Horizontal)                
            mCurLabel.Location = new System.Drawing.Point(0, Height - 13);
            else
                mCurLabel.Location = new System.Drawing.Point(Width-mCurLabel.Width, 0);            
            mCurLabel.Name = "lblCurValue";
            mCurLabel.TextAlign = ContentAlignment.TopCenter;
            mCurLabel.Size = new System.Drawing.Size(0, 100);
            mCurLabel.ForeColor = Color.White;

            
            // this call says "I'll draw it myself"
            this.SetStyle(ControlStyles.AllPaintingInWmPaint |
                ControlStyles.ResizeRedraw |
                ControlStyles.UserPaint, true);

            InitializeComponent();
            SetIndexes();

            this.Controls.Add(mCurLabel);
            this.Controls.Add(mMinLabel);
            this.Controls.Add(mMaxLabel);
        }

        #endregion

        #region Dispose function

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #endregion

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }

        #endregion

        #region Public properties
       
        public new void ResumeLayout(bool performLayout)
        {
            this.ResumeLayout();
            if (performLayout)
            {
                this.PerformLayout();
            }
        }

        /// <summary>
        /// Whether or not to show the current minimum value on the slider control (far left side). The format for this
        /// value is defined by the Format property.
        /// </summary>
        public bool ShowMinimumLabel
        {
            get { return mbShowMinLabel; }
            set
            {
                mbShowMinLabel = value;

                if (!mbSuspendLayout)
                {
                    DrawLabels();
                }
            }
        }

        /// <summary>
        /// Whether or not to show the current maximum value on the slider control (far right side). The format for this
        /// value is defined by the Format property.
        /// </summary>
        public bool ShowMaximumLabel
        {
            get { return mbShowMaxLabel; }
            set
            {
                mbShowMaxLabel = value;

                if (!mbSuspendLayout)
                {
                    DrawLabels();
                }
            }
        }

        /// <summary>
        /// Whether or not to show the current value on the slider control (centered on the puck position). The format for this
        /// value is defined by the Format property.
        /// </summary>
        public bool ShowValueLabel
        {
            get { return mbShowCurLabel; }
            set
            {
                mbShowCurLabel = value;

                if (!mbSuspendLayout)
                {
                    DrawLabels();
                }
            }
        }

        /// <summary>
        /// The maximum allowable date value for this slider control.
        /// </summary>
        public new int Minimum
        {
            get { return minValue; }
            set
            {
                // an interesting problem. in design mode you want this kind of
                // check, but at runtime you don't, as you will probably be 
                // resetting min and max at the same time. An alternative would 
                // be to have a SetMinMax function, but that isn't intuitive.
                // To get around this, call SuspendLayout and ResumeLayout.

                if (!mbSuspendLayout)
                {
                    if (value > Maximum)
                    {
                        return;
                    }
                }

                minValue = value;

                if (!mbSuspendLayout)
                {
                    SetIndexes();
                }
            }
        }

        /// <summary>
        /// The minimum allowable date value for this slider control.
        /// </summary>
        public new int Maximum
        {
            get { return maxValue; }
            set
            {
                // see comments within Minimum
                if (!mbSuspendLayout)
                {
                    if (value < Minimum)
                    {
                        return;
                    }
                }

                maxValue = value;

                if (!mbSuspendLayout)
                {
                    SetIndexes();
                }
            }
        }

        /// <summary>
        /// The current value for the slider, represented as a date.
        /// </summary>
        public new int Value
        {
            get { return currentValue; }
            set
            {

                if (!mbSuspendLayout)
                {
                    if (value < minValue)
                    {
                        return;
                    }

                    if (value > maxValue)
                    {                        
                        return;
                    }
                }

                currentValue = value;

                if (!mbSuspendLayout)
                {
                    SetIndexes();
                }
            }
        }

        /// <summary>
        /// Override of the SmallChange feature within a trackbar, this defines how far the puck
        /// moves when it is drug along its path.
        /// </summary>
        public new int SmallChange
        {
            get
            {
                return smallChange;
            }
            set
            {
                smallChange = value;
                SetIndexes();
            }
        }

        /// <summary>
        /// Override of the LargeChange feature within a trackbar, this defines how far the puck
        /// moves when it is clicked left or right of its current position.
        /// </summary>
        public new int LargeChange
        {
            get
            {
                return largeChange;
            }
            set
            {
                largeChange = value;
                if (!mbSuspendLayout)
                {
                    SetIndexes();
                }
            }
        }

        /// <summary>
        /// Override of the TickFrequency feature within a trackbar, this defines how far apart 
        /// the ticks are for the control.
        /// </summary>        
        public new int TickFrequency
        {
            get
            {
                return tickFrequency;
            }
            set
            {
                tickFrequency = value;
                if (!mbSuspendLayout)
                {
                    SetIndexes();
                }
            }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Call this function if you want to set several values within the slider,
        /// and you don't want consistancy checks until updates are comlete. When
        /// done, call ResumeLayout.
        /// SuspendLayout basically gives you free reign to set any values into the 
        /// control you want. Note that it will blow up with bad values later, though!
        /// </summary>
        public new void SuspendLayout()
        {
            mbSuspendLayout = true;
        }

        /// <summary>
        /// Call this function if you called SuspendLayout during control value
        /// setting.
        /// </summary>
        public new void ResumeLayout()
        {
            mbSuspendLayout = false;
            SetIndexes();
        }

        #endregion

        #region Control overrides

        /// <summary>
        /// Resize event
        /// </summary>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            DrawLabels();
        }
        
        /// <summary>
        /// OnPaint event.
        /// We are kind of tricking the system, because I want to paint 
        /// on top of the trackbar. The system either wants to draw it all
        /// and not send OnPaint events or let me do everything. So, we say
        /// I'll-do-it-no-you-do-it-okay-I'll-do-it.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            base.SetStyle(ControlStyles.UserPaint, false);
            base.Refresh();



            base.SetStyle(ControlStyles.UserPaint, true);
        }

        /// <summary>
        /// When the slider value is changed, we need to update our current value to the correct
        /// date representing that position.
        /// </summary>
        protected override void OnScroll(EventArgs e)
        {
            SetCurrentFromIndexChange();
            base.OnScroll(e);
        }

        /// <summary>
        /// When the slider value is changed, we need to update our current value to the correct
        /// date representing that position.
        /// </summary>
        protected override void OnValueChanged(EventArgs e)
        {
            SetCurrentFromIndexChange();
            base.OnValueChanged(e);
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Draw the labels and segment panel for our control, and put them in the 
        /// correct position based on current values.
        /// </summary>
        private void DrawLabels()
        {
            if (Orientation != Orientation.Horizontal)
            {
                if (Controls.Contains(mMinLabel))                this.Controls.Remove(mMinLabel);
                if (Controls.Contains(mMaxLabel))                 this.Controls.Remove(mMaxLabel);
            }
            

            mMinLabel.Visible = mbShowMinLabel;
            mMaxLabel.Visible = mbShowMaxLabel;
            mCurLabel.Visible = mbShowCurLabel;

            mMinLabel.Text = minValue.ToString();
            mMaxLabel.Text = maxValue.ToString();
            mCurLabel.Text = currentValue.ToString();

            // figure out where the centered current label should go
            if (mbShowCurLabel)
            {
                double dPercent = ((int)base.Value) / 100.0;
                double x = ((int)Width * dPercent) - mCurLabel.Width / 2;

                // stay within the bounds of the control
                if (x < 0) x = 0;
                if ((x + mCurLabel.Width) > Width) x = Width - mCurLabel.Width;
                if (base.Orientation == Orientation.Horizontal)
                    mCurLabel.Location = new Point((int)x, mCurLabel.Location.Y);
                else
                    mCurLabel.Location = new Point((int)x, (currentValue/maxValue)*Height );
            }

            // set up the position for the max value label
            if (mbShowMaxLabel)
            {
        		mMaxLabel.Location = new Point(Width - mMaxLabel.Width, mMaxLabel.Location.Y);
			}

		
        }

        /// <summary>
        /// Set min, max and current values within the control. Also calls DrawLabels,
        /// so this function pretty much performs all layout within the control.
        /// </summary>
        private void SetIndexes()
        {

            if (minValue == maxValue)
            {
                base.Enabled = false;
                base.Minimum = 0;
                base.Maximum = 0;
                base.Value = 0;

                mMinLabel.Visible = false;
                mMaxLabel.Visible = false;
                mCurLabel.Visible = false;

                return;
            }
            base.Enabled = true;

            int nTicksMin = minValue;
            int nTicksMax = maxValue;
            int nTicksCur = currentValue;

            if (nTicksCur < nTicksMin)
            {
                nTicksCur = nTicksMin;
                currentValue = nTicksCur;
            }
            if (nTicksCur > nTicksMax)
            {
                nTicksCur = nTicksMax;
                currentValue = nTicksCur;
            }

            double dCurPercent = 100.0 * (nTicksCur - nTicksMin) / (nTicksMax - nTicksMin);

            // we always go 0 to 100 in the base control, and just adjust our values to fit
            // within that ranges.
            base.Minimum = 0;
            base.Maximum = 100;

            int tsTotal = maxValue - minValue;
            base.SmallChange = Convert.ToInt32((smallChange / tsTotal) * 100.0);
            base.LargeChange = Convert.ToInt32((largeChange / tsTotal) * 100.0);
            base.TickFrequency = Convert.ToInt32((tickFrequency / tsTotal) * 100.0);

            base.Value = (int)dCurPercent;

            mMinLabel.Text = minValue.ToString();
            mMaxLabel.Text = maxValue.ToString();
            DrawLabels();
        }

        /// <summary>
        /// When the puck was changed through user interaction, update our current 
        /// value and labels based on the new value.
        /// </summary>
        private void SetCurrentFromIndexChange()
        {
            int nTicksMin = minValue;
            int nTicksMax = maxValue;

            double d1 = ((int)base.Value) / 100.0;
            double nTicksCur = d1 * (nTicksMax - nTicksMin) + nTicksMin;
            currentValue = (int)nTicksCur;
            DrawLabels();
        }

        #endregion
    }
}